import java.awt.Color;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Transparency;
import java.awt.image.AreaAveragingScaleFilter;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.CropImageFilter;
import java.awt.image.FilteredImageSource;
import java.awt.image.PixelGrabber;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.filechooser.FileSystemView;

public class ImageManager {
	
	BufferedImage currentImage;
	BufferedImage backedImage;
	
	private Rectangle gameFrameRect;
	public Rectangle gameGridRect;
	
	private int frameToGridOffsetX = 167;
	private int frameToGridOffsetY = 27;
	private int frameToGridWidth = 320;
	private int frameToGridHeight = 320;
	
	private int gridScaleWidth = 40;
	private int gridScaleHeight = 40;
	
	public int gridSizeX = 40;
	public int gridSizeY = 40;
	private int gridScaledDownSizeX = 5;
	private int gridScaledDownSizeY = 5;
	public int gridCountX = 8;
	public int gridCountY = 8;
	
	private int colorThreshold = 20;
	private int frameDetectionColorThreshold = 10;
	
	private Color yellowFilter = new Color(255, 255, 0);
	private Color orangeFilter = new Color(255, 116, 10);
	private Color greenFilter = new Color(85, 255, 130);
	private Color blueFilter = new Color(0, 60, 170);
	private Color purpleFilter = new Color(200, 0, 200);
	private Color redFilter = new Color(200, 0, 0);
	private Color whiteFilter = new Color(200, 200, 200);
	
	private int screenShotCount = 1;
	
	public ImageManager() {
		
	}
	
	public void CaptureScreen() {
		currentImage = Application.GetMouseRobot().CaptureScreen(new Rectangle(Application.GetScreenSize()));
		Application.GetWindow().SetImage(currentImage);
	}
	
	//Assumes a non-moving game frame
	//Assumes a fully-maximized web browser
	public void DetectGameFrame() {
		BackImage();
		if(currentImage == null) {
			System.out.println("No image in memory to detect game frame from.");
			return;
		}
		int imageWidth = currentImage.getWidth();
		int imageHeight = currentImage.getHeight();

		//Store default facebook background pixel
		int startX = 10;
		int startY = imageHeight / 2;
		ColorModel cm = ColorModel.getRGBdefault();
		int bgPixel = currentImage.getRGB(startX, startY);
		int bgRed = cm.getRed(bgPixel);
		int bgGreen = cm.getGreen(bgPixel);
		int bgBlue = cm.getBlue(bgPixel);
		
		//Travel right until we hit a non-background pixel
		int gameFrameXLeft;
		for(gameFrameXLeft=startX; gameFrameXLeft<imageWidth - 1; gameFrameXLeft++) {
			int pixel = currentImage.getRGB(gameFrameXLeft, startY);
			int red = cm.getRed(pixel);
			int green = cm.getGreen(pixel);
			int blue = cm.getBlue(pixel);
			int diff = (int) ((Math.abs(bgRed - red) + Math.abs(bgGreen - green) + Math.abs(bgBlue - blue)) / 3.0);
			if(diff > frameDetectionColorThreshold) {
				break;
			}
		}
		
		//Travel up until we hit a pixel which has a right-neighbor that's the same as bgPixel
		int gameFrameYTop;
		for(gameFrameYTop = startY; gameFrameYTop > 0; gameFrameYTop--) {
			int pixel = currentImage.getRGB(gameFrameXLeft, gameFrameYTop);
			int red = cm.getRed(pixel);
			int green = cm.getGreen(pixel);
			int blue = cm.getBlue(pixel);
			int diff = (int) ((Math.abs(bgRed - red) + Math.abs(bgGreen - green) + Math.abs(bgBlue - blue)) / 3.0);
			if(diff < frameDetectionColorThreshold) {
				break;
			}
		}
		
		//Travel down until we hit a pixel which has a right-neighbor that's the same as bgPixel
		int gameFrameYBottom;
		for(gameFrameYBottom = startY; gameFrameYBottom < imageHeight - 1; gameFrameYBottom++) {
			int pixel = currentImage.getRGB(gameFrameXLeft, gameFrameYBottom);
			int red = cm.getRed(pixel);
			int green = cm.getGreen(pixel);
			int blue = cm.getBlue(pixel);
			int diff = (int) ((Math.abs(bgRed - red) + Math.abs(bgGreen - green) + Math.abs(bgBlue - blue)) / 3.0);
			if(diff < frameDetectionColorThreshold) {
				break;
			}
		}
		
		//Travel right from 10 pixels down until we hit a pixel that's the same as bgPixel
		int gameFrameXRight;
		startY = gameFrameYTop + 10;
		for(gameFrameXRight = gameFrameXLeft; gameFrameXRight < imageWidth - 1; gameFrameXRight++) {
			int pixel = currentImage.getRGB(gameFrameXRight, startY);
			int red = cm.getRed(pixel);
			int green = cm.getGreen(pixel);
			int blue = cm.getBlue(pixel);
			int diff = (int) ((Math.abs(bgRed - red) + Math.abs(bgGreen - green) + Math.abs(bgBlue - blue)) / 3.0);
			if(diff < frameDetectionColorThreshold) {
				break;
			}
		}
		
		int gameFrameWidth = Math.max(1, gameFrameXRight - gameFrameXLeft);
		int gameFrameHeight = Math.max(1, gameFrameYBottom - gameFrameYTop);
		
		gameFrameRect = new Rectangle(gameFrameXLeft, gameFrameYTop, gameFrameWidth, gameFrameHeight);
		gameGridRect = new Rectangle(gameFrameRect.x + frameToGridOffsetX, gameFrameRect.y + frameToGridOffsetY,
									frameToGridWidth, frameToGridHeight);
		
		//BufferedImage gameFrameImage = image.getSubimage(gameFrameRect.x, gameFrameRect.y, gameFrameRect.width, gameFrameRect.height);
		CropImageFilter cropFilter = new CropImageFilter(gameGridRect.x, gameGridRect.y, gameGridRect.width, gameGridRect.height);
		Image croppedImage = Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(currentImage.getSource(), cropFilter));
		AreaAveragingScaleFilter scaleFilter = new AreaAveragingScaleFilter(gridScaleWidth, gridScaleHeight);
		Image scaledimage = Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(croppedImage.getSource(), scaleFilter));
		currentImage = ToBufferedImage(scaledimage);
		
		Application.GetWindow().SetImage(currentImage);
	}
	
	public void CaptureGameGrid() {
		currentImage = Application.GetMouseRobot().CaptureScreen(gameGridRect);
		AreaAveragingScaleFilter scaleFilter = new AreaAveragingScaleFilter(gridScaleWidth, gridScaleHeight);
		Image scaledimage = Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(currentImage.getSource(), scaleFilter));
		currentImage = ToBufferedImage(scaledimage);
		Application.GetWindow().SetImage(currentImage);
	}
	
	public void RunGemDetectionFilters(Gem[][] gems) {
		if(currentImage.getWidth() != gridScaleWidth || currentImage.getHeight() != gridScaleHeight) {
			System.out.println("current image is not of size " + Integer.toString(gridScaleWidth) + " by " + Integer.toString(gridScaleHeight));
			return;
		}
		RunGemDetectionFilter(gems, Gem.GemType.yellow, yellowFilter); //Yellow pass
		RunGemDetectionFilter(gems, Gem.GemType.orange, orangeFilter); //Orange pass
		RunGemDetectionFilter(gems, Gem.GemType.green, greenFilter); //Green pass
		RunGemDetectionFilter(gems, Gem.GemType.blue, blueFilter); //Blue pass
		RunGemDetectionFilter(gems, Gem.GemType.purple, purpleFilter); //Purple pass
		RunGemDetectionFilter(gems, Gem.GemType.red, redFilter); //Red pass
		RunGemDetectionFilter(gems, Gem.GemType.white, whiteFilter); //White pass
	}
	
	public void RunGemDetectionFilter(Gem[][] gems, Gem.GemType gemType, Color filter) {
		ColorModel cm = ColorModel.getRGBdefault();
		for(int x=0; x<currentImage.getWidth(); x++) {
			for (int y=0; y<currentImage.getHeight(); y++) {
				int pixel = currentImage.getRGB(x, y);
				int red = cm.getRed(pixel);
				int green = cm.getGreen(pixel);
				int blue = cm.getBlue(pixel);
				int diff = (int) ((Math.abs(filter.getRed() - red) + Math.abs(filter.getGreen() - green) + Math.abs(filter.getBlue() - blue)) / 3.0);
				if(diff < colorThreshold) {
					int gridX = (int) Math.floor((float)x/(float)gridScaledDownSizeX);
					int gridY = (int) Math.floor((float)y/(float)gridScaledDownSizeY);
					Gem.GemType currentGemType = gems[gridX][gridY].type;
					if(currentGemType != gemType && currentGemType != Gem.GemType.unknown) {
						if(currentGemType == Gem.GemType.none) {
							gems[gridX][gridY].type = gemType;
						} else {
							gems[gridX][gridY].type = Gem.GemType.unknown;
						}
					}
				}
			}
		}
	}
	
	public void SaveImage() {
		if(currentImage == null) {
			System.out.println("No image in memory.");
			return;
		}
		File imageFile = FileSystemView.getFileSystemView().createFileObject(Application.GetDesktopDirectory(), "image" + Integer.toString(screenShotCount++) + ".png");
		try {
			ImageIO.write(currentImage, "png", imageFile);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void RunColorFilter(int r, int g, int b) {
		BackImage();
		ColorModel cm = ColorModel.getRGBdefault();
		for(int x=0; x<currentImage.getWidth(); x++) {
			for (int y=0; y<currentImage.getHeight(); y++) {
				int pixel = currentImage.getRGB(x, y);
				int red = cm.getRed(pixel);
				int green = cm.getGreen(pixel);
				int blue = cm.getBlue(pixel);
				int diff = (int) ((Math.abs(r - red) + Math.abs(g - green) + Math.abs(b - blue)) / 3.0);
				if(diff < colorThreshold) {
					currentImage.setRGB(x, y, (255 << 24) | (255 << 16) | (255 << 8) | (255 << 0));
				} else {
					currentImage.setRGB(x, y, (255 << 24) | (0 << 16) | (0 << 8) | (0 << 0));
				}
			}
		}
		Application.GetWindow().SetImage(currentImage);
	}
	
	private void BackImage() {
		if(currentImage != null)
			backedImage = new BufferedImage(currentImage.getColorModel(), currentImage.copyData(null), currentImage.isAlphaPremultiplied(), null);
	}
	
	public void SetImage(File imageFile) {
		try {
			currentImage = ImageIO.read(imageFile);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void Undo() {
		if(backedImage != null && currentImage != null) {
			currentImage.flush();
			currentImage = null;
			currentImage = backedImage;
			Application.GetWindow().SetImage(currentImage);
		}
	}

	public BufferedImage ToBufferedImage(Image image) {
		if (image instanceof BufferedImage) {return (BufferedImage)image;}

		// This code ensures that all the pixels in the image are loaded
		image = new ImageIcon(image).getImage();

		// Determine if the image has transparent pixels
		boolean hasAlpha = HasAlpha(image);

		// Create a buffered image with a format that's compatible with the screen
		BufferedImage bimage = null;
		GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
		try {
			// Determine the type of transparency of the new buffered image
			int transparency = Transparency.OPAQUE;
			if (hasAlpha == true) {transparency = Transparency.BITMASK;}

			// Create the buffered image
			GraphicsDevice gs = ge.getDefaultScreenDevice();
			GraphicsConfiguration gc = gs.getDefaultConfiguration();
			bimage = gc.createCompatibleImage(image.getWidth(null), image.getHeight(null), transparency);
		} 
		catch (HeadlessException e) {} //No screen

		if (bimage == null) {
			// Create a buffered image using the default color model
			int type = BufferedImage.TYPE_INT_RGB;
			if (hasAlpha == true) {type = BufferedImage.TYPE_INT_ARGB;}
			bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
		}

		// Copy image to buffered image
		Graphics g = bimage.createGraphics();

		// Paint the image onto the buffered image
		g.drawImage(image, 0, 0, null);
		g.dispose();

		return bimage;
	}

	public boolean HasAlpha(Image image) {
		// If buffered image, the color model is readily available
		if (image instanceof BufferedImage) {return ((BufferedImage)image).getColorModel().hasAlpha();}

		// Use a pixel grabber to retrieve the image's color model;
		// grabbing a single pixel is usually sufficient
		PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);
		try {pg.grabPixels();} catch (InterruptedException e) {}

		// Get the image's color model
		if(pg.getColorModel() != null)
			return pg.getColorModel().hasAlpha();
		else 
			return false;
	}

}
